﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asol.Reporting.Support.XmlPersistor.Internal;

namespace Asol.Reporting.Support.XmlPersistor
{
	#region interface IXmlPersistNotify : dává objektu možnost být informován o procesech XmlPersist (Save / Load) pomocí property XmlPersistState
	/// <summary>
    /// interface IXmlPersistNotify : dává objektu možnost být informován o procesech XmlPersist (Save / Load) pomocí obsluhy set accessoru property XmlPersistState.
    /// Jakýkoli objekt, který chce být informován o tom, že je ukládán do XML / obnovován z XML, musí deklarovat interface IXmlPersistNotify.
    /// Pak do jeho property IXmlPersistNotify.XmlPersistState bude vkládána hodnota: při ukládání do XML hodnota Save (a po dokončení None), při načítání z XML hodnota Load a None.
	/// </summary>
	public interface IXmlPersistNotify
	{
        /// <summary>
        /// Aktuální stav procesu XML persistence.
        /// Umožňuje persistovanému objektu reagovat na ukládání nebo na načítání dat.
        /// Do této property vkládá XmlPersistor hodnotu odpovídající aktuální situaci.
        /// Datová instance může v set accessoru zareagovat a například připravit data pro Save, 
        /// anebo dokončit proces Load (navázat si další data nebo provést přepočty a další reakce).
        /// </summary>
		XmlPersistState XmlPersistState { get; set; }
	}
	/// <summary>
	/// Informuje objekt s daty, v jakém stavu je proces XmlPersist.
	/// Při zahájení Load (kdy jsou do objektu vkládána data načtená z XML) se do objektu, který má interface IXmlPersistNotify, 
	/// do property XmlPersistState vloží hodnota Load, a při zahájení Save hodnota Save. Po dokončení metody se vrátí hodnota None.
	/// Objekt na to může reagovat potlačením logiky navázané na Setování hodnot...
	/// </summary>
	public enum XmlPersistState
	{
        /// <summary>
        /// Nyní je objekt v klidu.
        /// Může přejít do stavu LoadBegin nebo SaveBegin.
        /// </summary>
		None = 0,
        /// <summary>
        /// Nyní začalo načítání dat do objektu.
        /// Po načtení bude stav změněn na LoadDone a pak None.
        /// </summary>
		LoadBegin,
        /// <summary>
        /// Nyní skončilo načítání dat do objektu.
        /// Okamžitě poté bude následovat vepsání stavu None.
        /// </summary>
        LoadDone,
        /// <summary>
        /// Nyní začalo ukládání dat z objektu.
        /// Po uložení bude stav změněn na SaveDone a pak None.
        /// </summary>
		SaveBegin,
        /// <summary>
        /// Nyní skončilo ukládání dat z objektu.
        /// Okamžitě poté bude následovat vepsání stavu None.
        /// </summary>
        SaveDone
	}
	#endregion
	#region interface IXmlSerializer : dává objektu možnost serializovat / deserializovat se vlastními silami.
	/// <summary>
	/// IXmlSerializer : dává objektu možnost serializovat / deserializovat se vlastními silami.
	/// Interface předepisuje jednu property XmlSerialData { get; set; }, 
	/// která v GET accessoru vrátí svoji vlastní serializaci,
	/// a která v SET accessoru převezme serializovaný text a naplní se z něj.
    /// Pro konverzi jednoduchých typů lze použít statické metody třídy Djs.Tools.XmlPersistor.Convertor, které konvertují datový typ z/na string.
	/// </summary>
	public interface IXmlSerializer
	{
		string XmlSerialData { get; set; }
	}
	#endregion
    #region class TypeConvertor : definuje pravidla pro persistenci specifického datového typu, obsahuje delegáty pro serializaci / deserializaci typu.
    /// <summary>
    /// TypeConvertor : Předpis pro konverzi jednoho typu.
    /// Popisuje jeden konkrétní Type, a uvádí delegáty na metody, které provedou serializaci / deserializaci tohoto typu.
    /// </summary>
    internal class TypeConvertor
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="dataType">Datový typ, pro který platá tato deklarace</param>
        /// <param name="persistenceType">Druh uložení. Pokud se bude používat serializátor</param>
        /// <param name="serializator">Metoda, která provede serializaci (z objektu na string). Musí být zadán pro persistenceType = XmlPersistenceType.Simple!</param>
        /// <param name="deserializator">Metoda, která provede deserializaci (ze stringu na objekt). Musí být zadán pro persistenceType = XmlPersistenceType.Simple!</param>
        public TypeConvertor(Type dataType, Func<object, string> serializator, Func<string, object> deserializator)
        {
            this.DataType = dataType;
            this.PersistenceType = XmlPersistenceType.Self;
            this.Serializator = serializator;
            this.Deserializator = deserializator;
        }
        /// <summary>
        /// Konstruktor interní
        /// </summary>
        /// <param name="dataType">Datový typ, pro který platá tato deklarace</param>
        /// <param name="persistenceType">Druh uložení. Pokud se bude používat serializátor</param>
        /// <param name="serializator">Metoda, která provede serializaci (z objektu na string). Musí být zadán pro persistenceType = XmlPersistenceType.Simple!</param>
        /// <param name="deserializator">Metoda, která provede deserializaci (ze stringu na objekt). Musí být zadán pro persistenceType = XmlPersistenceType.Simple!</param>
        internal TypeConvertor(Type dataType, XmlPersistenceType persistenceType, Func<object, string> serializator, Func<string, object> deserializator)
        {
            this.DataType = dataType;
            this.PersistenceType = persistenceType;
            this.Serializator = serializator;
            this.Deserializator = deserializator;
        }
        /// <summary>
        /// Datový typ, pro který platá tato deklarace
        /// </summary>
        public Type DataType { get; private set; }
        /// <summary>
        /// Druh uložení. Pokud bude zadána hodnota XmlPersistenceType.Simple, musí se vyplnit i Serializator a Deserializator.
        /// Pokud aplikace chce některé typy serializovat vlastními silami, uvede XmlPersistenceType.Simple a naplní svoje metody Serializator a Deserializator.
        /// </summary>
        internal XmlPersistenceType PersistenceType { get; private set; }
        /// <summary>
        /// Metoda, která provede serializaci (z objektu na string)
        /// </summary>
        public Func<object, string> Serializator { get; private set; }
        /// <summary>
        /// Metoda, která provede deserializaci (ze stringu na objekt)
        /// </summary>
        public Func<string, object> Deserializator { get; private set; }
    }
    #endregion
    #region Attribute classes: PropertyNameAttribute, CollectionItemNameAttribute, PersistingEnabledAttribute
    /// <summary>
    /// Abstraktní předek atributů XmlPersistoru
    /// </summary>
    public abstract class PersistAttribute : Attribute
	{ }
	/// <summary>
	/// Definuje jméno elementu, do něhož se ukládá hodnota této property.
	/// Pokud nebude specifikováno, jméno bude odvozeno ze jména property dle těchto příkladů:
	/// "PropertyName" = "property_name"; "_Ukazatel" = "_ukazatel"; "GID" = "gid", atd
	/// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
    public class PropertyNameAttribute : PersistAttribute
    {
        /// <summary>
        /// Definice jména elementu.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property.
        /// </summary>
        /// <param name="propertyName">
        /// Definuje jméno elementu, do něhož se ukládá hodnota této property.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property dle těchto příkladů:
        /// "PropertyName" = "property_name"; "_Ukazatel" = "_ukazatel"; "GID" = "gid", atd
        /// </param>
        public PropertyNameAttribute(string propertyName)
        {
            this.PropertyName = propertyName;
        }
        /// <summary>
        /// Definice jména elementu.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property.
        /// </summary>
        /// <param name="propertyOrder">Pořadí při serializaci, implicitní je 999999</param>
        public PropertyNameAttribute(int propertyOrder)
        {
            this.PropertyName = null;
            this.PropertyOrder = propertyOrder;
        }
        /// <summary>
        /// Definice jména elementu.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property.
        /// </summary>
        /// <param name="propertyName">
        /// Definuje jméno elementu, do něhož se ukládá hodnota této property.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property dle těchto příkladů:
        /// "PropertyName" = "property_name"; "_Ukazatel" = "_ukazatel"; "GID" = "gid", atd
        /// </param>
        /// <param name="propertyOrder">Pořadí při serializaci, implicitní je 0</param>
        public PropertyNameAttribute(string propertyName, int propertyOrder)
        {
            this.PropertyName = propertyName;
            this.PropertyOrder = propertyOrder;
        }
        /// <summary>
        /// Definice jména elementu.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property.
        /// </summary>
        /// <param name="propertyName">
        /// Definuje jméno elementu, do něhož se ukládá hodnota této property.
        /// Pokud nebude specifikováno, jméno bude odvozeno ze jména property dle těchto příkladů:
        /// "PropertyName" = "property_name"; "_Ukazatel" = "_ukazatel"; "GID" = "gid", atd
        /// </param>
        /// <param name="propertyOrder">Pořadí při serializaci, implicitní je 0</param>
        public PropertyNameAttribute(int propertyOrder, string propertyName)
        {
            this.PropertyName = propertyName;
            this.PropertyOrder = propertyOrder;
        }
        /// <summary>
        /// Název elemetnu v persistovaném XML dokumentu, do něhož se ukládá tato property
        /// </summary>
        public string PropertyName { get; private set; }
        /// <summary>
        /// Pořadí elementu v persistovaném XML dokumentu. 
        /// Implicitní je poslední pozice, elementy a atributy jsou řazeny podle PropertyOrder ASC, PropertyName ASC
        /// </summary>
        public Int32? PropertyOrder { get; private set; }
    }
	/// <summary>
	/// Definuje jméno elementu, do něhož se ukládají jednotlivé položky kolekce tohoto seznamu.
	/// Implicitní název je Item, ale tímto atributem jej lze předefinovat.
	/// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
    public class CollectionItemNameAttribute : PersistAttribute
	{
		/// <summary>
		/// Definice jména elementu.
		/// Pokud nebude specifikováno, jméno bude odvozeno ze jména property.
		/// </summary>
		/// <param name="itemName">
		/// Definuje jméno elementu, do něhož se ukládá hodnota této property.
		/// Pokud nebude specifikováno, jméno bude odvozeno ze jména property dle těchto příkladů:
		/// "PropertyName" = "property_name"; "_Ukazatel" = "_ukazatel"; "GID" = "gid", atd
		/// </param>
		public CollectionItemNameAttribute(string itemName)
		{
			this.ItemName = itemName;
		}
        /// <summary>
        /// Název elementu, který nese jednu položku kolekce
        /// </summary>
		public string ItemName { get; private set; }
	}
    /// <summary>
    /// Umožní nastavit výchozí potlačit ukládání všech properties, .
    /// Pokud není specifikováno, property se uloží, pokud k ní existuje SET metoda (i kdyby byla privátní).
    /// V některých případech je vhodné hodnotu neukládat (a nenačítat), například pokud property při setování hodnotu ukládá do dalších property.
    /// Rozhodně je nutno potlačit persistenci property, které referencují globální (systémové) objekty, ty by se jinak persistovaly také!!!
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public class PersistingDefaultAttribute : PersistAttribute
    {
        /// <summary>
        /// <para>
        /// Řídí persistenci vlastností (properties), které nemají atribut PersistingEnabled().
        /// </para>
        /// <para>
        /// Pokud je nastaven režim PersistingDefault(PersistingDefaultMode.Automatic), anebo není nastaven žádný režim, 
        /// pak se persistují všechny property které mají public set{} a nemají žádný atribut PersistingEnabled(), nebo ho mají true.
        /// </para>
        /// <para>
        /// V režimu PersistingDefault(PersistingDefaultMode.OnlyExplicit) se persistují jen ty property, které mají atribut PersistingEnabled(true).
        /// </para>
        /// </summary>
        /// <param name="defaultMode">Režim práce s property, které nemají atribut PersistingEnabled</param>
        public PersistingDefaultAttribute(PersistingDefaultMode defaultMode)
        {
            this.DefaultMode = defaultMode;
        }
        /// <summary>
        /// Tato property se má persistovat? true = ano, false = ne.
        /// Pokud tento atribut není přítomen, chápe se hodnota PersistingEnabled jako Ano.
        /// </summary>
        public PersistingDefaultMode DefaultMode { get; private set; }
    }
    /// <summary>
    /// Režim persistence prvků dané třídy
    /// </summary>
    public enum PersistingDefaultMode
    {
        /// <summary>
        /// Persistuje všechny property, které mají set{}, pokud nejsou označeny atributem PersistingEnabled(false)
        /// Persistuje tedy i neoznačené property.
        /// Tento režim je výchozí.
        /// </summary>
        Automatic,
        /// <summary>
        /// Persistuje pouze takové property, které mají set{}, a současně jsou označeny atributem PersistingEnabled(true).
        /// Ignoruje tedy neoznačené property.
        /// </summary>
        OnlyExplicit
    }
	/// <summary>
	/// Umožní potlačit ukládání této property.
	/// Pokud není specifikováno, property se uloží, pokud k ní existuje SET metoda (i kdyby byla privátní).
	/// V některých případech je vhodné hodnotu neukládat (a nenačítat), například pokud property při setování hodnotu ukládá do dalších property.
    /// Rozhodně je nutno potlačit persistenci property, které referencují globální (systémové) objekty, ty by se jinak persistovaly také!!!
	/// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
    public class PersistingEnabledAttribute : PersistAttribute
	{
        /// <summary>
        /// Tato property se má persistovat? true = ano, false = ne.
        /// Pokud tento atribut není přítomen, chápe se hodnota jako Ano.
        /// </summary>
		/// <param name="persistAndCloneEnable">Property lze persistovat i klonovat</param>
		public PersistingEnabledAttribute(bool persistAndCloneEnable)
		{
			this.PersistEnable = persistAndCloneEnable;
            this.CloneEnable = persistAndCloneEnable;
		}
        /// <summary>
        /// Tato property se má persistovat? true = ano, false = ne.
        /// Pokud tento atribut není přítomen, chápe se hodnota jako Ano.
        /// </summary>
        /// <param name="persistEnable">Property lze persistovat</param>
		/// <param name="cloneEnable">Property lze klonovat</param>
        public PersistingEnabledAttribute(bool persistEnable, bool cloneEnable)
        {
            this.PersistEnable = persistEnable;
            this.CloneEnable = cloneEnable;
        }
        /// <summary>
        /// Tato property se má persistovat? true = ano, false = ne.
        /// Pokud tento atribut není přítomen, chápe se hodnota PersistEnable jako Ano.
        /// </summary>
		public bool PersistEnable { get; private set; }
        /// <summary>
        /// Tato property se má klonovat? true = ano, false = ne.
        /// Pokud tento atribut není přítomen, chápe se hodnota CloneEnable jako Ano.
        /// Pokud je atribut vytvořen s pouze jednou hodnotou (persistAndCloneEnable), pak platí shodná pravidla pro persistování i klonování.
        /// </summary>
        public bool CloneEnable { get; private set; }
    }
	#endregion
}
